package org.timemachine.action.facadeBean;

import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;

import org.timemachine.action.facade.ChangeInfoFacade;
import org.timemachine.action.facade.EntityChangeFacade;
import org.timemachine.action.facade.ForwardEntityFacade;
import org.timemachine.factory.ChangeInfoFactory;
import org.timemachine.factory.EntityChangeFactory;
import org.timemachine.factory.spi.EntityChange;
import org.timemachine.model.base.ChangeInfo;
import org.timemachine.model.base.ForwardEntity;
import org.timemachine.tool.ClassHelperImpl;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class EntityChangeFacadeBean implements EntityChangeFacade {

	@PersistenceContext
	private EntityManager entityManager;

	@EJB
	private ChangeInfoFacade changeInfoFacade;
	
	@EJB
	private ForwardEntityFacade forwardEntityFacade;
	
	@Override
	public void createEntityChange(EntityChange entityChange) {

		ChangeInfo beginChangeInfo = ChangeInfoFactory.getNewInstance();
		changeInfoFacade.createChangeInfo(beginChangeInfo);
		
		entityChange.setBeginChangeInfo(beginChangeInfo);
		entityChange.setEndChangeInfo(changeInfoFacade.findInfiniteInstance());
		entityManager.persist(entityChange);
		
	}

	@Override
	public EntityChange updateEntityChange(EntityChange entityChange) {

		EntityChange previousEntityChange = (EntityChange) findPreviousEntityChange(entityChange);
		
		createEntityChange(entityChange);
		
		/** for application old entities, if there is no changed value, skip old changed time */
		if (previousEntityChange!=null) {
			previousEntityChange.setEndChangeInfo(entityChange.getBeginChangeInfo());
			return entityManager.merge(previousEntityChange);
		} else {
			return null;
		}
		
	}

	@Override
	public EntityChange removeEntityChange(EntityChange entityChange) {

		EntityChange previousEntityChange = (EntityChange) findPreviousEntityChange(entityChange);
		
		createEntityChange(entityChange);
		
		previousEntityChange.setEndChangeInfo(entityChange.getBeginChangeInfo());
		
		return entityManager.merge(previousEntityChange);
	}

	@Override
	public Object findPreviousEntityChange(Object entityChange) {
		return findPreviousEntityChange(entityChange, entityManager.getReference(ChangeInfo.class, 0L));
	}

	@Override
	public Object findPreviousEntityChange(Object entityChange, ChangeInfo endChangeInfo) {
		String primaryKeyName = ((EntityChange) entityChange).getEntityPrimaryKeyName();
		Object primaryKeyValue = ((EntityChange) entityChange).getEntityPrimaryKeyValue();
		
		Object previousEntityChange = null;
		try {
			previousEntityChange = entityManager.createQuery("select ec from " +
																	entityChange.getClass().getSimpleName() +
																	" ec " +
																	"where ec." + primaryKeyName + "= :entityId " +
																	" and ec.endChangeInfo = :endChangeInfo")
													.setParameter("entityId", primaryKeyValue)
													.setParameter("endChangeInfo", endChangeInfo)
													.getSingleResult();			
		} catch (NoResultException e) {
			// TODO: nothing to do
		} catch (Exception e) {
			previousEntityChange = entityManager.createQuery("select ec from " +
																entityChange.getClass().getSimpleName() +
																" ec " +
																"where ec." + primaryKeyName + "= :entityId " +
																" and ec.endChangeInfo = :endChangeInfo")
												.setParameter("entityId", primaryKeyValue)
												.setParameter("endChangeInfo", entityManager.getReference(ChangeInfo.class, 0L))
												.getResultList().get(0);			
			System.out.println("TM LOG: " + entityChange.getClass().getSimpleName() + " tablosunda coklu kayit hatasi. entityId = " + primaryKeyValue);
		}
		return previousEntityChange;
	}

	@Override
	public ChangeInfo getEndChangeInfo() {
		return entityManager.find(ChangeInfo.class, 0L);
	}
	
	@SuppressWarnings("unchecked")
	public EntityChange getEntityChange(Object entity) {
		ClassHelperImpl cHelper = new ClassHelperImpl();
		String primaryKeyName = cHelper.findPrimaryKeyName(entity);
		Object primaryKeyValue = cHelper.findPrimaryKeyValue(entity);
		List<EntityChange> list =  (List<EntityChange>) entityManager.createQuery("select e from " +
														entity.getClass().getSimpleName() + "Change e " +
														" where e.endChangeInfo.id = :changeId " +
														" and e." + primaryKeyName + "= :primaryKey "
														)
										.setParameter("changeId", getEndChangeInfo().getId())
										.setParameter("primaryKey", primaryKeyValue)
										.getResultList();
		if (list.size()>0) {
			return list.get(0);			
		} else {
			return null;
		}
	}

	public List<?> listAllByManyToOneEntity(String entityChangeName, String manyToOneEntityName, Integer manyToOneEntityId) {
		if (manyToOneEntityName == null || manyToOneEntityId == null) {
			return entityManager.createQuery("select " +
											" e " +
											" from " + entityChangeName + " e " +
											" where e.changeType = 3 " +
											" order by e.id "
											)
								.getResultList();				
		} else {
			return entityManager.createQuery("select " +
											" e " +
											" from " + entityChangeName + " e " +
											" join fetch e." + manyToOneEntityName + " m " +
											" where m.id = :id " +
											" and e.changeType = 3 " +
											" order by e.id "
											)
								.setParameter("id", manyToOneEntityId)
								.getResultList();	
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<EntityChange> listHistoricalData(Object entity, Object entityId, String manyToOneEntityName) {
		ClassHelperImpl cHelper = new ClassHelperImpl();
		String primaryKey = cHelper.findPrimaryKeyName(entity);
				
		if (manyToOneEntityName == null) {			
			return entityManager.createQuery("select e " +
											" from " + entity.getClass().getSimpleName() + "Change e join fetch e.beginChangeInfo c " +
											" where e." + primaryKey + " = :id " +
											" order by c.changeTimeStamp"
											)
								.setParameter("id", entityId)
								.getResultList();
		} else {
			return entityManager.createQuery("select e " +
											" from " + entity.getClass().getSimpleName() + "Change e join fetch e.beginChangeInfo c " +
											" join fetch e." + manyToOneEntityName + " m " +
											" where e." + primaryKey + " = :id " +
											" order by c.changeTimeStamp"
											)
								.setParameter("id", entityId)
								.getResultList();			
		}
	}

	@SuppressWarnings("unchecked")
	public List<EntityChange> listForwardData(Object entity, Object entityId, String manyToOneEntityName) {
		ClassHelperImpl cHelper = new ClassHelperImpl();
		String primaryKey = cHelper.findPrimaryKeyName(entity);
				
		if (manyToOneEntityName == null) {			
			return entityManager.createQuery("select e " +
											" from " + entity.getClass().getSimpleName() + "Change e join fetch e.beginChangeInfo c " +
											" where e." + primaryKey + " = :id " +
											" order by c.changeTimeStamp"
											)
								.setParameter("id", entityId)
								.getResultList();
		} else {
			return entityManager.createQuery("select e " +
											" from " + entity.getClass().getSimpleName() + "Change e join fetch e.beginChangeInfo c " +
											" join fetch e." + manyToOneEntityName + " m " +
											" where e." + primaryKey + " = :id " +
											" order by c.changeTimeStamp"
											)
								.setParameter("id", entityId)
								.getResultList();			
		}
	}

	@Override
	public void deleteForwardEntityChange(Integer changeId, Object entity) {
		Object change = EntityChangeFactory.convertChange(entity);
		EntityChange entityChange = (EntityChange) entityManager.find(change.getClass(), changeId);
		
		EntityChange prevEntityChange = (EntityChange) findPreviousEntityChange(entityChange, entityChange.getBeginChangeInfo());
		prevEntityChange.setEndChangeInfo(entityChange.getEndChangeInfo());
		entityManager.merge(prevEntityChange);

		ForwardEntity forwardEntity = forwardEntityFacade.findByForwardChangeInfoId(entityChange.getBeginChangeInfo().getId());
		forwardEntityFacade.remove(forwardEntity);

		entityManager.remove(entityChange);
		
		changeInfoFacade.remove(entityChange.getBeginChangeInfo());		
	}

}
